﻿namespace ClearLines.Bumblebee

open System
open System.Collections.Concurrent
open System.Threading
open System.Threading.Tasks

type Solver<'s> () =
    
   let foundSolution = new Event<SolutionMessage<'s>>()
   let mutable cancel = new CancellationTokenSource()

   let mutable activeBees = Hive.defaultActiveBees
   let mutable inactiveBees = Hive.defaultInactiveBees
   let mutable scoutBees = Hive.defaultScoutBees
   let mutable probaMistake = Hive.defaultProbaMistake
   let mutable probaConvince = Hive.defaultProbaConvince
   let mutable trips = Hive.defaultTrips

   [<CLIEvent>]
   member this.FoundSolution = foundSolution.Publish
   
   member this.ActiveBees
      with get() = activeBees
      and set(value) = 
         if value < 0 then 
            raise (ArgumentException("Active bees must be positive."))
         else activeBees <- value
          
   member this.ScoutBees
      with get() = scoutBees
      and set(value) =
         if value < 0 then 
            raise (ArgumentException("Scout bees must be positive."))
         else scoutBees <- value

   member this.InactiveBees
      with get() = inactiveBees
      and set(value) =
         if value < 1 then
            raise (ArgumentException("One inactive bee at least is required."))
         else inactiveBees <- value
      
   member this.ProbaMistake
      with get() = probaMistake
      and set(value) =
         if value < 0.0 || value > 1.0 then 
            raise (ArgumentException("ProbaMistake must be in [0.0, 1.0]."))
         else probaMistake <- value

   member this.ProbaConvince
      with get() = probaConvince
      and set(value) =
         if value < 0.0 || value > 1.0 then 
            raise (ArgumentException("ProbaConvince must be in [0.0, 1.0]."))
         else probaConvince <- value

   member this.Trips 
      with get() = trips
      and set(value) =
         if value < 0 then
            raise (ArgumentException("Trips must be positive."))
         else trips <- value
   
   /// <summary>Stops the search currently going on.</summary>
   member this.Stop() =
      cancel.Cancel()

   /// <summary>Starts searching for solutions to the Problem.</summary>
   member this.Search (problem: Problem<'s>) =
            
      cancel <- new CancellationTokenSource()

      let config =
         {  ActiveBees = this.ActiveBees; 
            ScoutBees = this.ScoutBees; 
            InactiveBees = this.InactiveBees; 
            ProbaMistake = this.ProbaMistake; 
            ProbaConvince = this.ProbaConvince
            Trips = this.Trips }

      let queue, initialState = this.Initialize(problem, config)
      foundSolution.Trigger(new SolutionMessage<'s>(fst(initialState)))

      let dispatch (inactives: Solution<'s> []) (queue: ConcurrentQueue<Bee<'s>>) bee =
         this.Dispatch problem config inactives queue bee

      let updateBest current candidate =
         if candidate.Quality > current.Quality
         then
            foundSolution.Trigger(new SolutionMessage<'s>(candidate))
            candidate
         else current
                                   
      let runQueue = new Action(fun () ->

         let rng = RNG.create()
         
         let rec loop ((queue: ConcurrentQueue<Bee<'s>>), (state: Solution<'s> * Solution<'s>[]), (cancel: CancellationTokenSource)) =
            if cancel.IsCancellationRequested then ignore ()
            else
               let success, bee = queue.TryDequeue()
               let updatedState =
                  if success then
                  // a returning bee has been found in the queue
                     let currentBest, currentInactives = state
                     let candidate = Hive.solutionOf bee

                     let updatedBest = updateBest currentBest candidate                                    
                     let updatedInactives = Hive.waggleUpdate rng config currentInactives bee

                     dispatch updatedInactives queue bee

                     updatedBest, updatedInactives
                  else state
               loop (queue, updatedState, cancel)   

         loop (queue, initialState, cancel))

      // Begin running the Search loop in a Task
      Task.Factory.StartNew(runQueue, cancel.Token)

   /// <summary>Initializes the Search: creates queue loaded with search bees, 
   /// and initial state (inactive solutions and initial best).</summary>
   member private this.Initialize(problem: Problem<'s>, config) =
      
      let rng = RNG.create()

      let generate = fun () -> 
         let solution = problem.Generate rng
         { Definition = solution; Quality = problem.Evaluate solution }
      let returnQueue = new ConcurrentQueue<Bee<'s>>()

      Hive.initialize config generate
         |> Seq.iter (fun bee -> returnQueue.Enqueue(bee))

      let initialState = Hive.initialState config generate
      returnQueue, initialState

   /// <summary>Initiates the next search for a bee:
   /// creates a new Task, sending a bee to search for a solution
   /// and return to the queue once the search completes.</summary>
   member private this.Dispatch (problem: Problem<'s>) config (inactives: Solution<'s> []) (queue: ConcurrentQueue<Bee<'s>>) bee =
      Task.Factory.StartNew (new Action (fun () ->
         let rng = RNG.create()
         let replacement() = inactives.[rng.Next(inactives.Length)]
         bee 
         |> Hive.redirect replacement config.Trips
         |> Hive.search rng problem config
         |> queue.Enqueue         
      )) |> ignore
